"""
Rings of elements with Fourier expansion and partially known relations. 

AUTHOR :
    -- Martin Raum (2009 - 07 - 27) Initial version
"""

#===============================================================================
# 
# Copyright (C) 2009 Martin Raum
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
#===============================================================================

from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ambient import GradedExpansionAmbient_abstract
from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_element import GradedExpansion_class
from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_functor import GradedExpansionBaseringInjection
from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_submodule import GradedExpansionSubmodule_abstract
from sage.algebras.algebra import Algebra
from sage.misc.flatten import flatten
from sage.misc.latex import latex
from sage.rings.all import Integer
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.structure.element import Element
import operator

#===============================================================================
# GradedExpansionRing_class
#===============================================================================

class GradedExpansionRing_class ( GradedExpansionAmbient_abstract, Algebra ) :
    """
    A class for a ring of graded expansions over an ambient of graded expansions,
    that might also be trivial.
    That is, a polynomial ring with relations and a mapping to an (equivariant)
    monoid power series.
    """
    
    def __init__ ( self, base_ring_generators, generators,
                   relations, grading, all_relations = True, reduce_before_evaluating = True) :
        """
        The degree one part of the monomials that correspond to generators over the
        base expansion ring will serve as the coordinates of the elements.
        
        INPUT:
            - ``base_ring_generators``      -- A list of (equivariant) monoid power series with
                                               coefficient domain the base ring of the coefficient
                                               domain of the generators or ``None``.
            - ``generators``                -- A list of (equivariant) monoid power series; The generators
                                               of the ambient over the ring generated by the base ring
                                               generators.
            - ``relations``                 -- An ideal in a polynomial ring with ``len(base_ring_generators) + len(generators)``
                                               variables.
            - ``grading``                   -- A grading deriving from :class:~`fourier_expansion_framework.gradedexpansions.gradedexpansion_grading`;
                                               A grading for the polynomial ring of the relations.
            - ``all_relations``             -- A boolean (default: ``True``); If ``True`` the relations given
                                               for the polynomial ring are all relations that the Fourier
                                               expansion have.
            - ``reduce_before_evaluating``  -- A boolean (default: ``True``); If ``True`` any monomial
                                               will be Groebner reduced before the Fourier expansion
                                               is calculated.

        NOTE:
            The grading must respect the relations of the generators.
        
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_ring import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1: 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1: 1, 2: 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: ger.base_ring()
            Graded expansion ring with generators a
       """
        if not hasattr(self, '_element_class') :
            self._element_class = GradedExpansion_class
        
        if hasattr(self, "_extended_base_ring") :
            Algebra.__init__(self, self._extended_base_ring)
        elif base_ring_generators is None or len(base_ring_generators) == 0 :
            Algebra.__init__(self, relations.base_ring())
        else :
            gb = filter( lambda p: all( all(a == 0 for a in list(e)[len(base_ring_generators):])
                                        for e in p.exponents() ),
                         relations.groebner_basis() )
            P = PolynomialRing( relations.base_ring(),
                                list(relations.ring().variable_names())[:len(base_ring_generators)] )
            base_relations = P.ideal(gb)
            R = GradedExpansionRing_class(None, base_ring_generators, base_relations,
                    grading.subgrading(xrange(len(base_ring_generators))), all_relations, reduce_before_evaluating)
            Algebra.__init__(self, R)

        GradedExpansionAmbient_abstract.__init__(self, base_ring_generators, generators, relations, grading, all_relations, reduce_before_evaluating)
        
        self._populate_coercion_lists_(
          coerce_list = [GradedExpansionBaseringInjection(self.base_ring(), self)],
          convert_list = [self.relations().ring()],
          convert_method_name = "_graded_expansion_submodule_to_graded_ambient_" )

    def _graded_monoms(self, index) :
        """
        Return all monoms in the generators that have a given grading index.
        
        INPUT:
            - ``index`` -- A grading values.
        
        OUTPUT:
            A list of elements of ``self``.
        
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_ring import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(None, Sequence([MonoidPowerSeries(mps, {1 : 4, 2 : 3}, mps.monoid().filter(4)), MonoidPowerSeries(mps, {1 : 1, 2 : 3}, mps.monoid().filter(4))]), PolynomialRing(ZZ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: ger._graded_monoms(3)
            [Graded expansion a^3, Graded expansion a*b]
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1 : 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1 : 4, 2 : 3}, mps.monoid().filter(4)), MonoidPowerSeries(mps, {1 : 1, 2 : 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b', 'c']).ideal(0), DegreeGrading((1,2,3)))
            sage: ger._graded_monoms(3)
            [Graded expansion a^3, Graded expansion a*b, Graded expansion c]
        """
        module_gens = self.grading().basis(index)
        module_gens = [ filter( lambda e: e != 1,
                                [ mon**ex if ex > 0 else 1
                                  for mon,ex in zip(self.basegens(), g[:self.nbasegens()]) + zip(self.gens(), g[self.nbasegens():]) ] )
                        for g in module_gens ]
        
        return [ self(reduce(operator.mul, g)) if len(g) > 1 else g[0] for g in module_gens ]

    def _coerce_map_from_(self, other) :
        """
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_module import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1: 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1: 1, 2: 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: ger._coerce_map_from_(ZZ)
        """
        if other is self.relations().ring() :
            from sage.structure.coerce_maps import CallableConvertMap
            
            return CallableConvertMap(other, self, self._element_constructor_)
        
        if isinstance(other, GradedExpansionSubmodule_abstract) :
            if other.graded_ambient() is self \
              or self.has_coerce_map_from(other.graded_ambient()) :
                from sage.structure.coerce_maps import CallableConvertMap
 
                return CallableConvertMap(other, self, other._graded_expansion_submodule_to_graded_ambient_)
 
        return Algebra._coerce_map_from_(self, other)

    def _element_constructor_(self, x) :
        """
        INPUT:
            - `x` -- An integer, an element of the underlying polynomial ring or
                     an element in a submodule of graded expansions.
        
        OUTPUT:
            An instance of the element class.
        
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_module import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1: 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1: 1, 2: 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: h = ger(1)
        """
        if isinstance(x, (int, Integer)) :
            return self._element_class(self, self.relations().ring()(x))
        
        P = x.parent()
        if P is self.relations().ring() :
            return self._element_class(self, x)
        elif self.relations().ring().has_coerce_map_from(P) :
            return self._element_class(self, self.relations().ring()(x))
        elif P is self.base_ring() and isinstance( self.base_ring(), GradedExpansionAmbient_abstract ) :
            return self._element_class(self, self.relations().ring()(x.polynomial()))

        return GradedExpansionAmbient_abstract._element_constructor_(self, x)

    def _repr_(self) :
        """
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_ring import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1: 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1: 1, 2: 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: ger
            Graded expansion ring with generators b
        """
        return "Graded expansion ring with generators "\
                + ''.join(map(lambda e: repr(e.polynomial()) + ", ", self.gens()))[:-2]
                
    def _latex_(self) :
        """
        TESTS::
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_basicmonoids import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_ring import *
            sage: from psage.modform.fourier_expansion_framework.monoidpowerseries.monoidpowerseries_element import *
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import DegreeGrading
            sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_ring import *
            sage: mps = MonoidPowerSeriesRing(QQ, NNMonoid(False))
            sage: ger = GradedExpansionRing_class(Sequence([MonoidPowerSeries(mps, {1: 1}, mps.monoid().filter(4))]), Sequence([MonoidPowerSeries(mps, {1: 1, 2: 3}, mps.monoid().filter(4))]), PolynomialRing(QQ, ['a', 'b']).ideal(0), DegreeGrading((1,2)))
            sage: latex(ger)
            Graded expansion ring with generators b
        """
        return "Graded expansion ring with generators "\
                + ''.join(map(lambda e: latex(e.polynomial()) + ", ", self.gens()))[:-2]
